{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/CoreTheGreat/HBPU-Machine-Learning-Course/blob/main/ML_Chapter4_Clustering.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "lPboLx_o0UxI"
   },
   "source": [
    "# 第四章：聚类\n",
    "湖北理工学院《机器学习》课程资料\n",
    "\n",
    "作者：李辉楚吴\n",
    "\n",
    "笔记内容概述: 密度聚类 DBSCAN\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "noyzyu_yp16b"
   },
   "source": [
    "### DBSCAN 聚类基本逻辑"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数据准备"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.datasets import make_moons\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Generate virtual data (Moon data)\n",
    "X_mm, y_mm = make_moons(n_samples=400, noise=0.05, random_state=42)\n",
    "\n",
    "# Normalize X_mm using z-score\n",
    "X_mm = (X_mm - np.min(X_mm, axis=0)) / (np.max(X_mm, axis=0)-np.min(X_mm, axis=0))\n",
    "\n",
    "label_size = 18 # Label size\n",
    "ticklabel_size = 14 # Tick label size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "-JIe2Q1XgTh2"
   },
   "source": [
    "数据分析与参数选择"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 551
    },
    "id": "g1hqSDWGgWah",
    "outputId": "af1c2d78-a4b6-4878-8d13-bb1bf4412f5e"
   },
   "outputs": [],
   "source": [
    "min_pts = 3\n",
    "\n",
    "# Comput Euclidean distance between samples\n",
    "distance_map = np.zeros((X_mm.shape[0], X_mm.shape[0]))\n",
    "for i in range(distance_map.shape[0]):\n",
    "    for j in range(distance_map.shape[1]):\n",
    "        distance_map[i,j] = np.linalg.norm(X_mm[i] - X_mm[j])\n",
    "\n",
    "# k-Distance\n",
    "k_distance = np.sort(np.sort(distance_map, axis=1)[:, min_pts])[::-1]\n",
    "\n",
    "# Draw k-Distance figure\n",
    "fig, ax = plt.subplots(figsize=(10,6))\n",
    "ax.plot(np.arange(1, len(k_distance)+1), k_distance, marker='o', linestyle='-', color='tab:blue')\n",
    "ax.set_xlabel('Sample Index', fontsize=label_size)\n",
    "ax.set_ylabel('k-Distance', fontsize=label_size)\n",
    "ax.tick_params(axis='both', which='major', labelsize=ticklabel_size) # Set tick label size\n",
    "plt.savefig(f'dbscan_kdistance.png', dpi=300)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gVhWKKp8xpRB"
   },
   "source": [
    "确定所有Core Point"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "1t2vSctG8fQ2",
    "outputId": "d72dbb0f-6642-4399-9207-554c47ee0b69"
   },
   "outputs": [],
   "source": [
    "r_eps = 0.04\n",
    "\n",
    "cluster_labels = np.zeros(X_mm.shape[0]) - 1 # \"-1\" means noise or unlabeled\n",
    "\n",
    "# Find all core points\n",
    "snap_noise_id = 0\n",
    "snap_corepoint_id = 0\n",
    "for i in range(len(cluster_labels)):\n",
    "    if np.sum(distance_map[i, :] <= r_eps) >= min_pts:\n",
    "        cluster_labels[i] = 0 # \"0\" means unclustered core point\n",
    "\n",
    "        if snap_corepoint_id < 3:\n",
    "            fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "            # Draw unlabeled or noise point\n",
    "            noise_idx = (cluster_labels == -1)\n",
    "            ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "            # Draw eps field of the current core point\n",
    "            corepoint_idx = (cluster_labels == 0)\n",
    "            circle = plt.Circle((X_mm[i, 0], X_mm[i, 1]), r_eps, edgecolor='red', facecolor='tab:red', alpha=0.5, zorder=1)\n",
    "            ax.add_patch(circle)\n",
    "\n",
    "            # Draw core points\n",
    "            ax.scatter(X_mm[corepoint_idx, 0], X_mm[corepoint_idx, 1], marker=\"o\", c='red', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            ax.set_title(f'Phase 1: Iteration {i}, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "            plt.axis('off')\n",
    "            # plt.savefig(f'dbscan_corepoint_{snap_corepoint_id+1}.png', dpi=300)\n",
    "            plt.show()\n",
    "\n",
    "            snap_corepoint_id += 1\n",
    "            \n",
    "    elif snap_noise_id < 3:\n",
    "        fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "        # Draw unlabeled or noise point\n",
    "        noise_idx = (cluster_labels == -1)\n",
    "        ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "        # Draw core points\n",
    "        corepoint_idx = (cluster_labels == 0)\n",
    "        ax.scatter(X_mm[corepoint_idx, 0], X_mm[corepoint_idx, 1], marker=\"o\", c='red', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "        # Draw eps field of the current point\n",
    "        circle = plt.Circle((X_mm[i, 0], X_mm[i, 1]), r_eps, edgecolor='blue', facecolor='tab:blue', alpha=0.5, zorder=1)\n",
    "        ax.add_patch(circle)\n",
    "\n",
    "        ax.set_title(f'Phase 1: Iteration {i}, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "        plt.axis('off')\n",
    "        # plt.savefig(f'dbscan_noise_{snap_noise_id+1}.png', dpi=300)\n",
    "        plt.show()\n",
    "\n",
    "        snap_noise_id += 1\n",
    "\n",
    "# Cluster all core points\n",
    "fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "# Draw unlabeled or noise point\n",
    "noise_idx = (cluster_labels == -1)\n",
    "ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "# Draw core points\n",
    "corepoint_idx = (cluster_labels == 0)\n",
    "ax.scatter(X_mm[corepoint_idx, 0], X_mm[corepoint_idx, 1], marker=\"o\", c='red', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "ax.set_title(f'Phase 1: Final, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "plt.axis('off')\n",
    "# plt.savefig(f'dbscan_corepoint_final.png', dpi=300)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "y05jO6mnxw4h"
   },
   "source": [
    "将互相可达的核心点合并成簇"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "olhR5Ow4yLEP",
    "outputId": "4b04e102-7722-4544-aabb-261be4ad7725"
   },
   "outputs": [],
   "source": [
    "# Scan unlabeled core points\n",
    "corepoint_cluster_labels = cluster_labels.copy()\n",
    "corepoint_indices = np.where(corepoint_cluster_labels == 0)[0]\n",
    "\n",
    "snap_corepoint_cluster_itr_idx = 0\n",
    "snap_corepoint_cluster_idx = 0\n",
    "\n",
    "cluster_id = 0 # Init cluster label\n",
    "\n",
    "# Clustering until all core points were successfully asigned labels\n",
    "while corepoint_cluster_labels[corepoint_indices].min() == 0:\n",
    "\n",
    "    cluster_id += 1 # Build new cluster\n",
    "\n",
    "    # Find start point, each cluster will be built completed in each loop\n",
    "    for start_corepoint_idx in corepoint_indices:\n",
    "        if corepoint_cluster_labels[start_corepoint_idx] == 0:\n",
    "            candidate_idx = np.array([start_corepoint_idx]) # Regard the single point as a cluster\n",
    "            break\n",
    "\n",
    "    # Repeat until no unlabeled points were found\n",
    "    while candidate_idx.size > 0:\n",
    "        if cluster_id == 2 and snap_corepoint_cluster_idx < 1:\n",
    "            fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "            # Draw noise points\n",
    "            noise_idx = (corepoint_cluster_labels == -1)\n",
    "            ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "            # Draw unlabeled core points\n",
    "            unlabel_idx = (corepoint_cluster_labels == 0)\n",
    "            ax.scatter(X_mm[unlabel_idx, 0], X_mm[unlabel_idx, 1], marker=\"o\", c=\"r\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "            # Draw cluster 1 core points\n",
    "            cluster1_idx = (corepoint_cluster_labels == 1)\n",
    "            ax.scatter(X_mm[cluster1_idx, 0], X_mm[cluster1_idx, 1], marker=\"o\", c='tab:blue', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            # Draw cluster 2 core points\n",
    "            cluster2_idx = (corepoint_cluster_labels == 2)\n",
    "            ax.scatter(X_mm[cluster2_idx, 0], X_mm[cluster2_idx, 1], marker=\"o\", c='yellow', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            ax.set_title(f'Phase 2: Cluster 1, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "            plt.axis('off')\n",
    "            # plt.savefig(f'dbscan_corepoint_cluster_1.png', dpi=300)\n",
    "            plt.show()\n",
    "\n",
    "            snap_corepoint_cluster_idx += 1\n",
    "\n",
    "        # Assign cluster label to the first candidate core point\n",
    "        corepoint_cluster_labels[candidate_idx[0]] = cluster_id\n",
    "        circle_id = candidate_idx[0]\n",
    "\n",
    "        # Find the neighbor core points of the first candidate core point\n",
    "        neighbor_idx = np.where(distance_map[candidate_idx[0]] <= r_eps)[0] # Find neighbor points\n",
    "        neighbor_idx = neighbor_idx[neighbor_idx != candidate_idx[0]] # Filter out itself\n",
    "        neighbor_idx = neighbor_idx[corepoint_cluster_labels[neighbor_idx] == 0] # Remain unlabeled core points\n",
    "\n",
    "        # Concatenate neighbor_idx to candidate_idx\n",
    "        candidate_idx = np.concatenate((candidate_idx, neighbor_idx))\n",
    "\n",
    "        # Remove the first candidate_idx core point\n",
    "        candidate_idx = np.delete(candidate_idx, 0)\n",
    "\n",
    "        # Remove duplicated points\n",
    "        candidate_idx = np.unique(candidate_idx)\n",
    "\n",
    "        if snap_corepoint_cluster_itr_idx < 3:\n",
    "            fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "            # Draw noise points\n",
    "            noise_idx = (corepoint_cluster_labels == -1)\n",
    "            ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "            # Draw unlabeled core points\n",
    "            unlabel_idx = (corepoint_cluster_labels == 0)\n",
    "            ax.scatter(X_mm[unlabel_idx, 0], X_mm[unlabel_idx, 1], marker=\"o\", c=\"r\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "            # Draw eps field of the current core point\n",
    "            circle = plt.Circle((X_mm[circle_id, 0], X_mm[circle_id, 1]), r_eps, edgecolor='blue', facecolor='tab:blue', alpha=0.5, zorder=1)\n",
    "            ax.add_patch(circle)\n",
    "\n",
    "            # Draw cluster 1 core points\n",
    "            cluster1_idx = (corepoint_cluster_labels == 1)\n",
    "            ax.scatter(X_mm[cluster1_idx, 0], X_mm[cluster1_idx, 1], marker=\"o\", c='tab:blue', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            # Draw cluster 2 core points\n",
    "            cluster2_idx = (corepoint_cluster_labels == 2)\n",
    "            ax.scatter(X_mm[cluster2_idx, 0], X_mm[cluster2_idx, 1], marker=\"o\", c='yellow', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            ax.set_title(f'Phase 2: Iteration {snap_corepoint_cluster_itr_idx+1}, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "            plt.axis('off')\n",
    "            # plt.savefig(f'dbscan_corepoint_cluster_Itr{snap_corepoint_cluster_itr_idx}.png', dpi=300)\n",
    "            plt.show()\n",
    "\n",
    "            snap_corepoint_cluster_itr_idx += 1\n",
    "\n",
    "# Display clustering result\n",
    "fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "# Draw noise points\n",
    "noise_idx = (corepoint_cluster_labels == -1)\n",
    "ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "# Draw unlabeled core points\n",
    "unlabel_idx = (corepoint_cluster_labels == 0)\n",
    "ax.scatter(X_mm[unlabel_idx, 0], X_mm[unlabel_idx, 1], marker=\"o\", c=\"r\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "# Draw cluster 1 core points\n",
    "cluster1_idx = (corepoint_cluster_labels == 1)\n",
    "ax.scatter(X_mm[cluster1_idx, 0], X_mm[cluster1_idx, 1], marker=\"o\", c='tab:blue', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "# Draw cluster 2 core points\n",
    "cluster2_idx = (corepoint_cluster_labels >= 2)\n",
    "ax.scatter(X_mm[cluster2_idx, 0], X_mm[cluster2_idx, 1], marker=\"o\", c=corepoint_cluster_labels[cluster2_idx], s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "ax.set_title(f'Phase 2: Final, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "plt.axis('off')\n",
    "# plt.savefig(f'dbscan_corepoint_cluster.png', dpi=300)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "QvcXFixnVxyJ"
   },
   "source": [
    "将满足条件的边缘点Border Point加入簇中"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "3KS5UbePo-_Z",
    "outputId": "7c573939-e110-4e52-9325-f6003f255f75"
   },
   "outputs": [],
   "source": [
    "borderpoint_labels = corepoint_cluster_labels.copy()\n",
    "\n",
    "# Get unlabeled points\n",
    "candidate_indices = np.where(borderpoint_labels == -1)[0]\n",
    "\n",
    "snap_border_itr = 0\n",
    "\n",
    "# Scan candidate_idx to find border points\n",
    "for candidate_idx in candidate_indices:\n",
    "\n",
    "    # Find the neighbor points of i\n",
    "    neighbor_idx = np.where(distance_map[candidate_idx] <= r_eps)[0] # Find neighbor points\n",
    "    neighbor_idx = neighbor_idx[neighbor_idx != candidate_idx] # Filter out itself\n",
    "    neighbor_idx = neighbor_idx[borderpoint_labels[neighbor_idx] > 0] # Remain core points\n",
    "\n",
    "    # Candidate point is a border point\n",
    "    if len(neighbor_idx) > 0:\n",
    "\n",
    "        # Assign the closest core point's cluster label to the candicate point\n",
    "        distance_to_corepoint = distance_map[candidate_idx, neighbor_idx]\n",
    "        closest_corepoint_idx = neighbor_idx[np.argmin(distance_to_corepoint)]\n",
    "        borderpoint_labels[candidate_idx] = borderpoint_labels[closest_corepoint_idx]\n",
    "\n",
    "        if snap_border_itr < 3:\n",
    "            # Display clustering result\n",
    "            fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "            # Draw noise points\n",
    "            noise_idx = (borderpoint_labels == -1)\n",
    "            ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "            # Draw eps field of the current core point\n",
    "            circle = plt.Circle((X_mm[candidate_idx, 0], X_mm[candidate_idx, 1]), r_eps, edgecolor='red', facecolor='tab:red', alpha=0.5, zorder=1)\n",
    "            ax.add_patch(circle)\n",
    "\n",
    "            # Draw cluster 1 core points\n",
    "            cluster1_idx = (borderpoint_labels == 1)\n",
    "            ax.scatter(X_mm[cluster1_idx, 0], X_mm[cluster1_idx, 1], marker=\"o\", c='tab:blue', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            # Draw cluster 2 core points\n",
    "            cluster2_idx = (borderpoint_labels >= 2)\n",
    "            ax.scatter(X_mm[cluster2_idx, 0], X_mm[cluster2_idx, 1], marker=\"o\", c=borderpoint_labels[cluster2_idx], s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "            ax.set_title(f'Phase 3: Itr {snap_border_itr}, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "            plt.axis('off')\n",
    "            # plt.savefig(f'dbscan_border_{snap_border_itr}.png', dpi=300)\n",
    "            plt.show()\n",
    "\n",
    "            snap_border_itr += 1\n",
    "\n",
    "# Display clustering result\n",
    "fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "# Draw noise points\n",
    "noise_idx = (borderpoint_labels == -1)\n",
    "ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "# Draw cluster 1 core points\n",
    "cluster1_idx = (borderpoint_labels == 1)\n",
    "ax.scatter(X_mm[cluster1_idx, 0], X_mm[cluster1_idx, 1], marker=\"o\", c='tab:blue', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "# Draw cluster 2 core points\n",
    "cluster2_idx = (borderpoint_labels >= 2)\n",
    "ax.scatter(X_mm[cluster2_idx, 0], X_mm[cluster2_idx, 1], marker=\"o\", c=borderpoint_labels[cluster2_idx], s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "ax.set_title(f'Phase 3: Final, eps = {r_eps}, minPts = {min_pts}', fontsize=label_size)\n",
    "plt.axis('off')\n",
    "# plt.savefig(f'dbscan_border_final.png', dpi=300)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "EPJRDqroj_na"
   },
   "source": [
    "人工调整参数后的结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 850
    },
    "id": "9qRRN2OBkDl5",
    "outputId": "5f6e35b8-362c-46fd-d537-4babfee454e1"
   },
   "outputs": [],
   "source": [
    "import time\n",
    "from sklearn.cluster import DBSCAN\n",
    "\n",
    "mdl_dbscan_eps = 0.05\n",
    "mdl_dbscan_minpts = 3\n",
    "\n",
    "# Define DBSCAN model\n",
    "mdl_dbscan = DBSCAN(eps=mdl_dbscan_eps, min_samples=mdl_dbscan_minpts)\n",
    "\n",
    "# Train model\n",
    "start_time = time.time()\n",
    "mdl_dbscan.fit(X_mm)\n",
    "end_time = time.time()\n",
    "\n",
    "print(f'Training time: {end_time - start_time:.2f} seconds')\n",
    "\n",
    "labels_mdl_dbscan = mdl_dbscan.labels_\n",
    "\n",
    "# Display clustering result\n",
    "fig, ax = plt.subplots(figsize=(10,10))\n",
    "\n",
    "# Draw noise points\n",
    "noise_idx = (labels_mdl_dbscan == -1)\n",
    "ax.scatter(X_mm[noise_idx, 0], X_mm[noise_idx, 1], marker=\"o\", c=\"k\", s=10**2, edgecolor=\"k\", zorder=0)\n",
    "\n",
    "# Draw cluster 1 core points\n",
    "cluster1_idx = (labels_mdl_dbscan == 0)\n",
    "ax.scatter(X_mm[cluster1_idx, 0], X_mm[cluster1_idx, 1], marker=\"o\", c='tab:blue', s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "# Draw cluster 2 core points\n",
    "cluster2_idx = (labels_mdl_dbscan >= 1)\n",
    "ax.scatter(X_mm[cluster2_idx, 0], X_mm[cluster2_idx, 1], marker=\"o\", c=labels_mdl_dbscan[cluster2_idx], s=10**2, edgecolor=\"k\", zorder=2)\n",
    "\n",
    "ax.set_title(f'Adjust parameter manually: eps = {mdl_dbscan_eps}, minPts = {mdl_dbscan_minpts}', fontsize=label_size)\n",
    "plt.axis('off')\n",
    "# plt.savefig(f'mdl_dbscan.png', dpi=300)\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "authorship_tag": "ABX9TyO+9EvQyLwzW6sRWnzqnjPu",
   "collapsed_sections": [
    "c-Xj4WtjPdwD"
   ],
   "include_colab_link": true,
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
